package nl.utwente.ewi.hmi.multitouch.analysis;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.Map.Entry;

import javolution.util.FastMap;
import javolution.util.FastSet;
import nl.utwente.ewi.hmi.multitouch.TouchDeviceInfo;
import nl.utwente.ewi.hmi.multitouch.analysis.matching.SegmentMatching;
import nl.utwente.ewi.hmi.multitouch.util.Pair;
import nl.utwente.ewi.hmi.multitouch.util.Properties;

public class GreedyHypothesisCreator extends HypothesisCreator {

	private Map<Segment, NavigableSet<Segment>> trajectories = null;
	
	private Map<Segment, NavigableSet<Segment>> trajectoriesBack = null;
	
	private Map<Pair<Segment, Segment>, Double> confidenceCache = null;
	
	private static final float CONFIDENCE_THRESHOLD = 0.7f;
	private static final float JOIN_THRESHOLD = 0.01f;
	
	private SegmentMatching matching = null;
	
	public GreedyHypothesisCreator(TouchDeviceInfo deviceInfo) {
		super(deviceInfo);

		this.trajectories = new HashMap<Segment, NavigableSet<Segment>>(); //FastMap.newInstance();
		this.trajectoriesBack = new HashMap<Segment, NavigableSet<Segment>>();// FastMap.newInstance();
		this.confidenceCache = new HashMap<Pair<Segment, Segment>, Double>(); //FastMap.newInstance();
		
		this.matching = Properties.MATCHING_FACTORY.getSegmentMatching(deviceInfo);
	}

	@Override
	public Set<Hypothesis> getHypotheses(Set<Segment> newState) {
		confidenceCache.clear();
		
		Set<Hypothesis> hypotheses = new HashSet<Hypothesis>();
		
		Set<Segment> fromSegments = new HashSet<Segment>(trajectories.keySet()); //FastSet.newInstance();
//		fromSegments.addAll(trajectories.keySet());
		Set<Segment> toSegments = new HashSet<Segment>(newState); //FastSet.newInstance();
//		toSegments.addAll(newState);
		
		Map<Segment, Segment> oldToNew = new HashMap<Segment, Segment>(); //FastMap.newInstance();
		Map<Segment, Segment> newToOld = new HashMap<Segment, Segment>(); //FastMap.newInstance();

		boolean createdNewHypotheses = false;

		do {
			newToOld.clear();
			oldToNew.clear();
			for(Segment toSegment : toSegments) {
				Segment bestFromSegment = null;
				
				double maxConfidence = 0;
				
				for(Segment fromSegment : fromSegments) {
					double confidence = this.getConfidence(fromSegment, toSegment);

					if(confidence > CONFIDENCE_THRESHOLD && confidence > maxConfidence) {
						bestFromSegment = fromSegment;
						maxConfidence = confidence;
					}
				}

				if(bestFromSegment == null) {
					continue;
				}
	
				// If there is a contending segment ...
				if(oldToNew.containsKey(bestFromSegment)) {
					Segment contenderSegment = oldToNew.get(bestFromSegment);
					double contenderConfidence = getConfidence(bestFromSegment, contenderSegment);

					// ... pick the best segment
					if(contenderConfidence < maxConfidence) {
						oldToNew.put(bestFromSegment, toSegment);
						newToOld.remove(contenderSegment);
						newToOld.put(toSegment, bestFromSegment);
					}
				// else, add the new relation
				} else {
					oldToNew.put(bestFromSegment, toSegment);
					newToOld.put(toSegment, bestFromSegment);
				}
			}

			createdNewHypotheses = !newToOld.isEmpty();

			for(Entry<Segment, Segment> relation : newToOld.entrySet()) {
				Segment toSegment = relation.getKey();
				Segment fromSegment = relation.getValue();

				toSegments.remove(toSegment);
				fromSegments.remove(fromSegment);
				
				hypotheses.add(new Hypothesis(fromSegment, toSegment));
			}
		} while(createdNewHypotheses);

		// TODO : optimize by swapping hypotheses 
		
		Iterator<Segment> fromIterator = fromSegments.iterator();
		// for each from segment, find the best toSegment and see whether its hypotheses is the best
		while(fromIterator.hasNext()) {
			Segment fromSegment = fromIterator.next();
			Segment bestTarget = null;
			double bestConfidence = 0;

			for(Hypothesis hypothesis : hypotheses) {
				double currentConfidence = this.getConfidence(hypothesis.getFromSegment(), hypothesis.getToSegment());
				double competingConfidence = this.getConfidence(fromSegment, hypothesis.getToSegment());

				if(competingConfidence <= bestConfidence) {
					continue;
				}

				if(Math.abs(currentConfidence - competingConfidence) < JOIN_THRESHOLD) {
					bestConfidence = competingConfidence;
					bestTarget = hypothesis.getToSegment();
				}
			}
			if(bestTarget != null) {
				hypotheses.add(new Hypothesis(fromSegment, bestTarget));
				fromIterator.remove();
			}
		}

		Iterator<Segment> toIterator = toSegments.iterator();
		// for each to segment, find the best fromSegment and see whether its hypotheses is the best
		while(toIterator.hasNext()) {
			Segment toSegment = toIterator.next();
			Segment bestSource = null;
			double bestConfidence = 0;

			for(Hypothesis hypothesis : hypotheses) {
				double currentConfidence = this.getConfidence(hypothesis.getFromSegment(), hypothesis.getToSegment());
				double competingConfidence = this.getConfidence(hypothesis.getFromSegment(), toSegment);

				if(competingConfidence <= bestConfidence) {
					continue;
				}

				if(Math.abs(currentConfidence - competingConfidence) < JOIN_THRESHOLD) {
					bestConfidence = competingConfidence;
					bestSource = hypothesis.getFromSegment();
				}
			}
			if(bestSource != null) {
				hypotheses.add(new Hypothesis(bestSource, toSegment));
				toIterator.remove();
			}
		}
		
		// End of trajectory hypotheses
		for(Segment fromSegment : fromSegments) {
			hypotheses.add(new Hypothesis(fromSegment, null));
		}
		
		// New trajectory hypotheses
		for(Segment toSegment : toSegments) {
			hypotheses.add(new Hypothesis(null, toSegment));
		}

		// Update the trajectory cache
		this.updateTrajectories(hypotheses);

//		FastMap.recycle(newToOld);
//		FastMap.recycle(oldToNew);

//		FastSet.recycle(toSegments);
//		FastSet.recycle(fromSegments);
		
		return hypotheses;
	}

	private double getConfidence(Segment fromSegment, Segment toSegment) {
		Pair<Segment, Segment> key = new Pair<Segment, Segment>(fromSegment, toSegment);

		if(confidenceCache.containsKey(key)) {
			return confidenceCache.get(key);
		}

		double confidence = matching.getConfidence(trajectories.get(fromSegment), toSegment);

		confidenceCache.put(key, confidence);

		return confidence;
	}
	
	private void updateTrajectories(Set<Hypothesis> hypotheses) {
		this.trajectoriesBack.clear();

		for(Hypothesis hypothesis : hypotheses) {

			if(hypothesis.isEndOfTrajectory()) {
				continue;
			}

			NavigableSet<Segment> trajectory = trajectories.get(hypothesis.getFromSegment());

			if(trajectory == null) {
				trajectoriesBack.put(hypothesis.getToSegment(), new TreeSet<Segment>(Arrays.asList(hypothesis.getToSegment())));
			} else {
				trajectory.add(hypothesis.getToSegment());

				if(trajectory.size() > 2) {
					trajectory.pollFirst();
				}

				trajectoriesBack.put(hypothesis.getToSegment(), trajectory);
			}
		}

		Map<Segment, NavigableSet<Segment>> temp = this.trajectories;

		this.trajectories = trajectoriesBack;
		this.trajectoriesBack = temp;

	}
}
